import importlib.util
import os
import tomllib
import sys
import traceback
import importlib

from fastapi import FastAPI
from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates
from fastapi.templating import Jinja2Templates

from . import debug
from . import config


icons_access: dict = {}
template_access: dict = {}
afterrun_lst = []
exports_lst = {}
addition_js_lst = ""
addition_css_lst = ""
permission_info_lst = {}


def import_from_path(path: str):
    # 准备基本信息
    modulename = path.split(os.sep)[-1].replace(".py", "")
    spec = importlib.util.spec_from_file_location(
        path.split(os.sep)[-1].replace(".py", ""), path)

    if (spec is None):
        return None
    if (spec.loader is None):
        return None

    # 构造 module
    mymodule = importlib.util.module_from_spec(spec)

    mymodule.__name__ = modulename
    mymodule.__package__ = '.'.join(
        os.path.relpath(os.path.dirname(path)).split(os.sep))  # 修补相对导入
    mymodule.__path__ = [os.path.dirname(path)]

    spec.loader.exec_module(mymodule)  # 执行

    return mymodule


def load_api():  # 注册api到python包路径
    sys.path.append(os.path.dirname(__file__)+os.sep+"api")


def load_plugin_name(app, plugin_name: str, paths):
    global afterrun_lst, addition_js_lst, addition_css_lst
    if not os.path.exists(paths+os.sep+plugin_name+os.sep+"manifest.toml"):
        return
    if (plugin_name.endswith(".disable")):  # 添加插件禁用后缀
        return
    if (plugin_name.startswith(".cache")):
        return
    print("==> Try to load plugin: "+plugin_name)
    with open(paths+os.sep+plugin_name+os.sep+"manifest.toml", "rb") as file:
        try:
            manifest = tomllib.load(file)
        except:
            print("  --> Error: load maifest failed")
            return
    # 检查基本信息缺失
    if ("common" not in manifest):
        print("  --> Error: manifest error")
        return
    if ("name" not in manifest["common"]):
        print("  --> Error: manifest error")
        return
    if ("icon" in manifest):
        if ("name" not in manifest["icon"]):
            print("  --> Error: manifest icon error")
            return
        if ("image" not in manifest["icon"]):
            print("  --> Error: manifest icon error")
            return
        if not (os.path.exists(paths+os.sep+plugin_name+os.sep+manifest["icon"]["image"])):
            print("  --> Error: manifest icon error")
            return
        print("  --> Access icon: "+manifest["icon"]["name"])
        with open(paths+os.sep+plugin_name+os.sep+manifest["icon"]["image"], "rb") as file:
            icons_access[plugin_name] = (
                manifest["icon"]["name"], file.read())
    # 注册窗口
    if ("window" in manifest):
        if ("path" not in manifest["window"]):
            print("  --> Error: manifest window error")
            return
        print("  --> Access windows: "+manifest["window"]["path"])
        template_access[plugin_name] = {}
        templates = Jinja2Templates(
            directory=paths+os.sep+plugin_name+os.sep+manifest["window"]["path"])
        if ("static" in manifest["window"]):  # 绑定静态资源
            if not (os.path.exists(paths+os.sep+plugin_name+os.sep+manifest["window"]["static"])):
                print("  --> Error: manifest static error")
            else:
                print("  --> Access static: " +
                      manifest["window"]["static"])
                app.mount("/api/plugin/"+plugin_name+"/static", StaticFiles(directory=paths+os.sep+plugin_name+os.sep+manifest["window"]["static"]),
                          name="plugin_static_"+plugin_name)
            if ("iframe_mirror" in manifest):  # 绑定iframe到资源镜像，不推荐使用
                for k, v in manifest["iframe_mirror"].items():
                    if not (os.path.exists(paths+os.sep+plugin_name+os.sep+manifest["window"]["static"]+os.sep+v)):
                        print("    --> Error: manifest mirror static error")
                    else:
                        print("    --> Access mirror static: " +
                              k)
                        app.mount("/api/plugin/"+plugin_name+"/iframe/"+k, StaticFiles(directory=paths+os.sep+plugin_name+os.sep+manifest["window"]["static"]+os.sep+v),
                                  name="plugin_static_mirror_"+plugin_name+"_"+k)
        for j in os.listdir(paths+os.sep+plugin_name+os.sep+manifest["window"]["path"]):
            print("    --> Access window: "+j)
            # 缓存模板
            template_access[plugin_name][j] = (
                templates.TemplateResponse(j, {"request": {}, "root": "/api/plugin/"+plugin_name+"/iframe", "api": "/api/plugin/"+plugin_name+"/api", "self": "/api/plugin/"+plugin_name+"/iframe/"+j, "static": "/api/plugin/"+plugin_name+"/static", "winapi": "/static/js/api.js", "version": config.version, "work_dir": config.work_dir, "plugin_dir": os.path.abspath(paths+os.sep+plugin_name)}))
    if ("api" in manifest):  # 注册api
        if ("path" not in manifest["api"]):
            print("  --> Error: manifest api error")
            return
        if ("object" not in manifest["api"]):
            print("  --> Error: manifest api error")
            return
        if not (os.path.exists(paths+os.sep+plugin_name+os.sep+manifest["api"]["path"])):
            print("  --> Error: manifest api error")
        else:
            print("  --> Load api: " +
                  manifest["api"]["path"])
            try:
                app_obj = import_from_path(
                    paths+os.sep+plugin_name+os.sep+manifest["api"]["path"])
                # 实际注册api
                pluginobj = getattr(
                    app_obj, manifest["api"]["object"])
                routeobj = pluginobj.route
                for k, v in routeobj.routes_get.items():
                    app.get("/api/plugin/"+plugin_name+"/api"+k)(v)
                    print("    --> Load api: "+k+"[GET]")
                for k, v in routeobj.routes_post.items():
                    app.post("/api/plugin/"+plugin_name+"/api"+k)(v)
                    print("    --> Load api: "+k+"[POST]")
                for k, v in routeobj.routes_delete.items():
                    app.delete("/api/plugin/"+plugin_name+"/api"+k)(v)
                    print("    --> Load api: "+k+"[DELETE]")
                for k, v in routeobj.routes_ws.items():
                    app.websocket("/api/plugin/"+plugin_name+"/api"+k)(v)
                    print("    --> Load api: "+k+"[WEBSOCKET]")
                for k, v in routeobj.routes_proxy.items():
                    app.api_route("/api/plugin/"+plugin_name+"/api"+k, methods=[
                                  "GET", "POST", "PUT", "DELETE", "OPTIONS", "PATCH", "HEAD"])(v)
                    print("    --> Load api: "+k+"[PROXY]")
                afterrun_lst += pluginobj.after_run
                exports_lst[plugin_name] = pluginobj.export.exports
            except Exception:  # 错误格式化输出
                print("  --> Error: load api failed: ")
                print(
                    '\n'.join(["    -->"+i for i in traceback.format_exc().split("\n")]))
                return
        if ("permission" in manifest["api"]):  # 注册权限提示
            if not (os.path.exists(paths+os.sep+plugin_name+os.sep+manifest["api"]["permission"])):
                print("  --> Error: manifest api error")
            try:
                with open(paths+os.sep+plugin_name+os.sep+manifest["api"]["permission"], "rb") as file:
                    permission_info_lst[plugin_name] = tomllib.load(file)
            except:
                print("  --> Error: load permission failed")
        else:
            permission_info_lst[plugin_name] = {}

    if ("addition" in manifest):  # 注册自动加载的 js/css
        if ("js" in manifest["addition"]):
            print("  --> Load addition js: " +
                  manifest["addition"]["js"])
            if not (os.path.exists(paths+os.sep+plugin_name+os.sep+manifest["addition"]["js"])):
                print("  --> Error: manifest addition error")
            else:
                with open(paths+os.sep+plugin_name+os.sep+manifest["addition"]["js"], "r") as file:
                    addition_js_lst = f"// {plugin_name}\n" + \
                        addition_js_lst + file.read()+"\n"
        if ("css" in manifest["addition"]):
            print("  --> Load addition css: " +
                  manifest["addition"]["css"])
            if not (os.path.exists(paths+os.sep+plugin_name+os.sep+manifest["addition"]["css"])):
                print("  --> Error: manifest addition error")
            else:
                with open(paths+os.sep+plugin_name+os.sep+manifest["addition"]["css"], "r") as file:
                    addition_css_lst = f"/* {plugin_name} */\n" + \
                        addition_css_lst + file.read()+"\n"
        if ("py" in manifest["addition"]):
            print("  --> Load addition script: " +
                  manifest["addition"]["py"])
            if not (os.path.exists(paths+os.sep+plugin_name+os.sep+manifest["addition"]["css"])):
                print("  --> Error: manifest addition error")
            else:
                try:
                    # 直接导入模块
                    app_obj = import_from_path(
                        paths+os.sep+plugin_name+os.sep+manifest["addition"]["py"])
                except Exception:  # 错误格式化输出
                    print("  --> Error: load addition failed: ")
                    print(
                        '\n'.join(["    -->"+i for i in traceback.format_exc().split("\n")]))
                    return


def load_plugin(app: FastAPI):
    load_api()

    global icons_access

    plugin_load_paths = ["plugins"]+config.read("plugin")["user_import_paths"]

    for p in plugin_load_paths:
        if (not os.path.exists(p)):
            continue
        # 初始化检测用文件
        with open(p+os.sep+".NEUTRON_PLUGIN", "w") as file:
            file.write("Neutron panel plugin check file. Do not remove.")

        # 加载插件
        print("==> Load plugin in \""+p+"\"")
        for i in os.listdir(p):
            load_plugin_name(app, i, p)
            debug.watch_plug(p+os.sep+i)

    # 加载单个插件
    print("==> Load single plugins")
    for i in config.read("plugin")["load_single_plugins"]:
        with open(i+os.sep+".."+os.sep+".NEUTRON_PLUGIN", "w") as file:
            file.write("Neutron panel plugin check file. Do not remove.")
        load_plugin_name(app, i.split("/")[-1], os.path.dirname(i))
        debug.watch_plug(i)

    print("--> Run after script")  # 执行最后脚本
    for i in afterrun_lst:
        try:
            i()
        except Exception:  # 错误格式化输出
            print("  --> Error: run after script failed: ")
            print(
                '\n'.join(["    -->"+i for i in traceback.format_exc().split("\n")]))
            return
